using System;
using System.Collections.Generic;
using System.Text;
using Sedna.Domain.Enum;

namespace Sedna.Domain.GenericRange
{
    public class TimeRange : Range<Time>
    {
        public static readonly TimeRange EMPTY = new TimeRange(null, null);

        private DateTimePrecision precision;

        # region Constructors

        public TimeRange(Time start, Time end)
            : this(start, end, DateTimePrecision.ToMillisecond)
        {
        }

        public TimeRange(Time start, Time end, DateTimePrecision precision)
            : base(start, end)
        {
            Precision = precision;
        }

        # endregion

        # region Factory Methods

        public static TimeRange UpTo(Time end)
        {
            return new TimeRange(null, end);
        }

        public static TimeRange StartingOn(Time start)
        {
            return new TimeRange(start, null);
        }

        # endregion

        # region Properties

        public DateTimePrecision Precision
        {
            get { return precision; }
            set
            {
                switch (value)
                {
                    case DateTimePrecision.ToHour:
                    case DateTimePrecision.ToMinute:
                    case DateTimePrecision.ToSecond:
                    case DateTimePrecision.ToMillisecond:
                        {
                            break;
                        }
                   default:
                        {
                            throw new ArgumentException(
                                String.Format("This time precision '{0}' is not supported.", value));
                        }
                }
                
                precision = value;
            }
        }

        # endregion

        public TimeSpan Difference()
        {
            return Difference(precision);
        }
        
        /// <summary>
        /// As far [Start = End] is not an empty range then
        /// is this case difference will be greater than zero.
        /// </summary>
        public TimeSpan Difference(DateTimePrecision precision)
        {
            DateTimePrecision currPrecision = this.precision;

            // To perform internal operations with specified precision
            this.precision = precision;

            try
            {
                if (IsEmpty)
                    return new TimeSpan();

                TimePart part;

                switch (precision)
                {
                    case DateTimePrecision.ToHour:
                        {
                            part = TimePart.Hour;
                            break;
                        }
                    case DateTimePrecision.ToMinute:
                        {
                            part = TimePart.Minute;
                            break;
                        }
                    case DateTimePrecision.ToSecond:
                        {
                            part = TimePart.Second;
                            break;
                        }
                    case DateTimePrecision.ToMillisecond:
                        {
                            part = TimePart.Millisecond;
                            break;
                        }
                    default:
                        throw new ArgumentException(
                            String.Format("This time precision '{0}' is not supported.", precision));
                }

                return new TimeSpan(0, 0, 0, 0, End.Convert(precision)./*see summary*/Add(1, part).TotalMilliseconds - Start.Convert(precision).TotalMilliseconds);
            }
            finally
            {
                this.precision = currPrecision;
            }
        }

        public TimeRange Gap(Range<Time> range, DateTimePrecision precision)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");

            // To perform internal operations with specified precision

            DateTimePrecision thisCurrPrecision = this.precision;
            DateTimePrecision thatCurrPrecision = precision; // just to initialize

            if (range is TimeRange)
            {
                thatCurrPrecision = (range as TimeRange).Precision;
                (range as TimeRange).Precision = precision;
            }

            this.precision = precision;           

            try
            {
                if (Overlaps(range)) return EMPTY;
                
                Range<Time> lower, higher;

                if (CompareTo(range) < 0)
                {
                    lower = this;
                    higher = range;
                }
                else
                {
                    lower = range;
                    higher = this;
                }

                return EvalGap(lower, higher, precision);
            }
            finally
            {
                this.precision = thisCurrPrecision;

                if (range is TimeRange)
                {
                    (range as TimeRange).Precision = thatCurrPrecision;
                }
            }
        }


        protected override bool Equals(Time t1, Time t2)
        {
            return t1.Equals(t2, precision);
        }

        protected override int Compare(Time t1, Time t2)
        {
            return t1.CompareTo(t2, precision);
        }

        protected override Range<Time> EvalGap(Range<Time> lower, Range<Time> higher)
        {
            return EvalGap(lower, higher, precision);
        }

        protected TimeRange EvalGap(Range<Time> lower, Range<Time> higher, DateTimePrecision precision)
        {
            TimePart part;
            
            switch (precision)
            {
                case DateTimePrecision.ToHour:
                    {
                        part = TimePart.Hour;
                        break;                        
                    }
                case DateTimePrecision.ToMinute:
                    {
                        part = TimePart.Minute;
                        break;
                    }
                case DateTimePrecision.ToSecond:
                    {
                        part = TimePart.Second;
                        break;
                    }
                case DateTimePrecision.ToMillisecond:
                    {
                        part = TimePart.Millisecond;
                        break;
                    }
                default:
                    throw new ArgumentException(
                            String.Format("This time precision '{0}' is not supported.", precision));
            }

            return new TimeRange(lower.End.Add(1, part), higher.Start.Add(-1, part), precision);
        }

        protected override Range<Time> EvalEmpty
        {
            get { return EMPTY; }
        }

        protected override bool IsStartInitialized
        {
            get { return Start != null; }
        }

        protected override bool IsEndInitialized
        {
            get { return End != null;}
        }

        protected override bool IsTypeValid(Range<Time> range)
        {
            if (range == null)
                throw new ArgumentNullException("Range cannot be null");

            return range is TimeRange;
        }        
    }
}
